<?php
/**
 * S3
 * 
 * @author ShuangYa
 * @package Blog
 * @category Addon
 * @link http://www.sylingd.com/
 * @copyright Copyright (c) 2015 ShuangYa
 * @license http://lab.sylingd.com/go.php?name=blog&type=license
 */

class S3SimpleSdk {
	
	protected $accessKeyId;
	protected $secretAccessKey;
	protected $endpoint;
	protected $version;
	protected $region;
	protected $bucket;
	
	public function __construct($config) {
		$this->accessKeyId = $config['access_key_id'];
		$this->secretAccessKey = $config['secret_access_key'];
		$this->endpoint = $config['endpoint'];
		$this->version = isset($config['version']) ? $config['version'] : 'latest';
		$this->region = $config['region'];
		$this->bucket = $config['bucket'];
	}
	
	/**
	 * 创建请求签名
	 */
	private function sign($method, $uri, $headers, $payloadHash, $expires = null) {
		$algorithm = 'AWS4-HMAC-SHA256';
		$service = 's3';
		$date = gmdate('Ymd\THis\Z');
		$scope = gmdate('Ymd') . '/' . $this->region . '/' . $service . '/aws4_request';
		
		// 规范请求
		$canonicalUri = $uri;
		$canonicalQueryString = '';
		$canonicalHeaders = '';
		$signedHeadersArray = [];
		
		foreach ($headers as $key => $value) {
			$canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
			$signedHeadersArray[] = strtolower($key);
		}
		sort($signedHeadersArray);
		$signedHeaders = implode(';', $signedHeadersArray);
		
		$canonicalRequest = $method . "\n" .
			$canonicalUri . "\n" .
			$canonicalQueryString . "\n" .
			$canonicalHeaders . "\n" .
			$signedHeaders . "\n" .
			$payloadHash;
			
		$stringToSign = $algorithm . "\n" .
			$date . "\n" .
			$scope . "\n" .
			hash('sha256', $canonicalRequest);
			
		// 签名密钥
		$kSecret = "AWS4" . $this->secretAccessKey;
		$kDate = hash_hmac('sha256', gmdate('Ymd'), $kSecret, true);
		$kRegion = hash_hmac('sha256', $this->region, $kDate, true);
		$kService = hash_hmac('sha256', $service, $kRegion, true);
		$kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
		$signature = hash_hmac('sha256', $stringToSign, $kSigning);
		
		if ($expires) {
			return [
				'credentials' => $this->accessKeyId . '/' . $scope,
				'X-Amz-Algorithm' => $algorithm,
				'X-Amz-Credential' => $this->accessKeyId . '/' . $scope,
				'X-Amz-Date' => $date,
				'X-Amz-SignedHeaders' => $signedHeaders,
				'X-Amz-Expires' => $expires,
				'X-Amz-Signature' => $signature
			];
		} else {
			return [
				'Authorization' => $algorithm . ' ' .
					'Credential=' . $this->accessKeyId . '/' . $scope . ', ' .
					'SignedHeaders=' . $signedHeaders . ', ' .
					'Signature=' . $signature,
				'X-Amz-Date' => $date
			];
		}
	}
	
	/**
	 * 上传对象到S3
	 * @param string $local 本地文件路径
	 * @param string $remote 远程对象键名
	 * @return bool 是否成功
	 */
	public function putObject($local, $remote) {
		if (!file_exists($local)) {
			return false;
		}
		
		$fileContent = file_get_contents($local);
		$contentLength = strlen($fileContent);
		$contentHash = hash('sha256', $fileContent);
		
		$uri = '/' . ltrim($remote, '/');
		$headers = [
			'Host' => parse_url($this->endpoint, PHP_URL_HOST),
			'Content-Length' => $contentLength,
			'Content-Type' => mime_content_type($local)
		];
		
		$authHeaders = $this->sign('PUT', $uri, $headers, $contentHash);
		$headers = array_merge($headers, $authHeaders);
		
		$url = $this->endpoint . $uri;
		
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
		curl_setopt($ch, CURLOPT_POSTFIELDS, $fileContent);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_HEADER, true);
		curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function($k, $v) {
			return "$k: $v";
		}, array_keys($headers), $headers));
		
		$response = curl_exec($ch);
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
		curl_close($ch);
		
		return $httpCode >= 200 && $httpCode < 300;
	}
	
	/**
	 * 从S3删除对象
	 * @param string $remote 远程对象键名
	 * @return bool 是否成功
	 */
	public function deleteObject($remote) {
		$uri = '/' . ltrim($remote, '/');
		$headers = [
			'Host' => parse_url($this->endpoint, PHP_URL_HOST)
		];
		
		$payloadHash = hash('sha256', '');
		$authHeaders = $this->sign('DELETE', $uri, $headers, $payloadHash);
		$headers = array_merge($headers, $authHeaders);
		
		$url = $this->endpoint . $uri;
		
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_HEADER, true);
		curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function($k, $v) {
			return "$k: $v";
		}, array_keys($headers), $headers));
		
		$response = curl_exec($ch);
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
		@curl_close($ch);
		
		return $httpCode >= 200 && $httpCode < 300;
	}
	
	/**
	 * 创建预签名URL
	 * @param string $remote 远程对象键名
	 * @param int $expires 过期时间(秒)
	 * @return string 预签名URL
	 */
	public function createPresignedUrl($remote, $expires = 3600) {
		$uri = '/' . ltrim($remote, '/');
		$headers = [
			'Host' => parse_url($this->endpoint, PHP_URL_HOST)
		];
		
		$payloadHash = hash('sha256', '');
		$queryParams = $this->sign('GET', $uri, $headers, $payloadHash, $expires);
		
		$queryString = http_build_query($queryParams);
		$url = $this->endpoint . $uri . '?' . $queryString;
		
		return $url;
	}
}